using System;
using RayDen.Library.Components.PhotonMapping;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Components;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Samplers;

namespace RayDen.RayEngine.Engines.PhotonMapping
{
    public enum PhotonMappingMode
    {
        DirectOnly,
        SeparateEstimation,
        FinalGathering,
    }

    public class PhotonMappingContext : IPathSamplerAggregator
    {

        internal float indirectRadius = 20f;
        internal int indirectPhotons = 100;
        internal float causticRadius = 10.5f;
        internal int causticPhotons = 5;

        internal int finalGatheringPhotons = 10;
        internal float finalGatheringRadius = 5.75f;
        public static PhotonMappingMode Mode = PhotonMappingMode.FinalGathering;

        public int FinalGatheringRays = 16;
        public bool ScaleMaps = true;
        internal bool mapPreviewMode = false;

        public uint MaxGlobalPhotons = 20000;
        public uint MaxCausticPhotons = 10000;

        public int MaxPhotonDepth = 15;

        protected internal IPhotonMap globalPhotonMap, causticPhotonMap;

        #region PhotonSampler
        class PhotonSampler : PathSamplingEngine<bool>
        {
            protected internal uint MaxGlobalPhotons, MaxCausticPhotons;
            protected internal IPhotonMap globalPhotonMap, causticPhotonMap;
            protected IRandomProvider rnd;


            public PhotonSampler(IIntersectionEngine engine, IRayEngineScene scn, IRadianceSampler ss,
                IRandomProvider r, IPhotonMap gl, IPhotonMap cst, uint maxglob, uint maxcst):base(scn,engine)
            {
                this.SurfaceSampler = ss;
                this.intersectionEngine = engine;
                this.scene = scn;

                this.rnd = r;
                this.globalPhotonMap = gl;
                this.causticPhotonMap = cst;
                this.MaxGlobalPhotons = maxglob;
                this.MaxCausticPhotons = maxcst;
            }

            protected override PathInfo GeneratePhoton(IRayEngineScene scn, IRandomProvider rnd)
            {
                float lPdf;
                LightSample lightSample;
                var scene = (RayEngineScene)scn;
                var light = scene.Lights[scene.SampleLights(rnd.NextFloat(), out lPdf)];
            reeval:
                light.EvaluatePhoton(scene, rnd.NextFloat(), rnd.NextFloat(), rnd.NextFloat(), rnd.NextFloat(),
                                     rnd.NextFloat(),
                                     out lightSample);
                if (lightSample.Pdf <= 0f)
                    goto reeval;
                return new PathInfo
                {
                    Ray = lightSample.LightRay,
                    RayIndex = -1,
                    Throughput = (RgbSpectrum)(RgbSpectrumInfo)(lightSample.Spectrum) / (lightSample.Pdf * lPdf)
                };
            }

            protected override bool IsSamplingDone()
            {
                var done = globalPhotonMap.PhotonsCount >= this.MaxGlobalPhotons - 1
                            &&
                            causticPhotonMap.PhotonsCount >= this.MaxCausticPhotons - 1;

                if (globalPhotonMap.PhotonsCount >= this.MaxGlobalPhotons - 1 && causticPhotonMap.PhotonsCount == 0)
                    done = true;
                var cl = Console.CursorLeft;
                var ct = Console.CursorTop;
                Console.Write("{0} - {1} ", globalPhotonMap.PhotonsCount, causticPhotonMap.PhotonsCount);
                Console.SetCursorPosition(cl, ct);
                return done;
            }

            protected override bool OnSamplingDone()
            {
                return true;
            }

            protected override bool OnPathHit(ref Point hitPoint, ref Vector wo, ref Vector wi, SurfaceIntersectionData hitInfo, ref RgbSpectrum f, float fPdf, PathInfo photonPath)
            {
                if (f.IsBlack() || fPdf <= 0f)
                {
                    return false;
                }

                if (hitInfo.MMaterial.IsDiffuse())
                {
                    switch (Mode)
                    {
                        case PhotonMappingMode.FinalGathering:
                            globalPhotonMap.Store(ref photonPath.Throughput, ref hitPoint, ref photonPath.Ray.Dir);

                            if (photonPath.Data)
                            {
                                causticPhotonMap.Store(ref photonPath.Throughput, ref hitPoint,
                                                       ref photonPath.Ray.Dir);
                                return false;
                            }
                            break;
                        case PhotonMappingMode.DirectOnly:
                            return false;
                        case PhotonMappingMode.SeparateEstimation:
                            if (photonPath.Depth > 1)
                            {
                                globalPhotonMap.Store(ref photonPath.Throughput, ref hitPoint,
                                                      ref photonPath.Ray.Dir);
                            }

                            if (photonPath.Data)
                            {
                                causticPhotonMap.Store(ref  photonPath.Throughput, ref hitPoint, ref photonPath.Ray.Dir);
                                return false;
                            }
                            break;
                    }
                }
                else
                {
                    //if (photons[index].Depth < 3)
                    photonPath.Data = true;
                }

                RgbSpectrum anew = photonPath.Throughput * f * Vector.AbsDot(ref wi, ref hitInfo.ShadingNormal) / fPdf;
                float continueProb = Math.Min(1f, anew.y() / photonPath.Throughput.y());
                if (rnd.NextFloat() > continueProb || photonPath.Depth > MaxPhotonDepth || photonPath.Throughput.IsBlack())
                {
                    return false;
                }

                photonPath.Throughput *= anew / continueProb;

                photonPath.Ray = new RayData(ref hitPoint, ref wi);
                return true;
            }

        }
        #endregion

        public IImageFilm Film;

        public SurfaceSampler surfaceSampler;


    


        public bool GatheringStarted;

        public bool PhotonMapsComplete
        {
            get
            {
                return this.globalPhotonMap.PhotonsCount >= MaxGlobalPhotons - 2
                    || this.causticPhotonMap.PhotonsCount > MaxCausticPhotons - 2;
            }
        }


        public PhotonMappingContext()
        {

            this.globalPhotonMap =
                //new ModernHashGridPhotonMap((int)MaxGlobalPhotons);
            new KdTreePhotonMap((int)MaxGlobalPhotons);
            this.causticPhotonMap =
                //new ModernHashGridPhotonMap((int)MaxCausticPhotons);
            new KdTreePhotonMap((int)MaxCausticPhotons);
        }



        public void PrepareMaps()
        {
            try
            {

                Tracer.TraceLine("Building maps {0} global {1} caustic", globalPhotonMap.PhotonsCount, causticPhotonMap.PhotonsCount);
                this.causticPhotonMap.Balance();
                this.globalPhotonMap.Balance();
            }
            catch (Exception ex)
            {

                Tracer.TraceLine(ex.Message);
                throw;
            }

        }

        public void BuildMaps(IIntersectionEngine intersectionDevice, RayEngineScene scene)
        {
            if (Mode == PhotonMappingMode.DirectOnly)
                return;
            IRandomProvider rnd = new FastRandom();
            var sampler = new PhotonSampler(intersectionDevice, scene, surfaceSampler, rnd, globalPhotonMap, causticPhotonMap, MaxGlobalPhotons, MaxCausticPhotons);
            sampler.StartSampling();
            Tracer.TraceLine("Photons collected. start building maps");
            this.PrepareMaps();
            if (ScaleMaps)
            {
                this.globalPhotonMap.ScalePhotonMap(1f / sampler.PhotonsGenerated);
                this.causticPhotonMap.ScalePhotonMap(1f / sampler.PhotonsGenerated);
            }
            Tracer.TraceLine("Rendering - {0} Mode ", Mode);

        }


        public void StoreGlobal(ref Point pos, ref Vector dir, ref RgbSpectrum power)
        {
            this.globalPhotonMap.Store(ref power, ref pos, ref dir);
        }

        public void StoreCaustic(ref Point pos, ref Vector dir, ref RgbSpectrum power)
        {
            this.causticPhotonMap.Store(ref power, ref pos, ref dir);
        }

        #region IPathSamplerAggregator Members

        public void Consume(PathSamplerBase path)
        {

        }

        public void UpdateScreenBuffer()
        {
            this.Film.UpdateScreenBuffer();
        }

        public void Reset()
        {

        }

        public float[] GetScreenBuffer()
        {
            return this.Film.GetScreenBuffer();
        }

        #endregion

    }
}